package com.wanrue.tcc.repository;

import com.alibaba.fastjson.JSON;
import com.wanrue.tcc.context.Participant;
import com.wanrue.tcc.context.Transaction;
import com.wanrue.tcc.context.Xid;
import com.wanrue.tcc.enums.Result;
import com.wanrue.tcc.enums.Role;
import com.wanrue.tcc.enums.Status;
import com.wanrue.tcc.recover.RecoveryInfo;
import com.wanrue.tcc.serializer.JdkSerializationSerializer;
import com.wanrue.tcc.serializer.ObjectSerializer;
import com.wanrue.tcc.utils.StringUtils;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;

/**
 * jdbc  实现
 * Created by wenjie on 10/25/18.
 */
@Log4j
public class JdbcRepository implements Repository {

    @Setter
    private String domain;

    @Setter
    private String tbSuffix;

    private JdbcTemplate jdbcTemplate;

    private ObjectSerializer serializer = new JdkSerializationSerializer();

    public JdbcRepository(DataSource dataSource, String domain) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.domain = domain;
    }

    @Override
    public int create(Transaction transaction) {
        String sql = "INSERT INTO " + getTableName() + "(GLOBAL_TX_ID, BRANCH_QUALIFIER, DOMAIN, PARTNER,PARTNER_STR, STATUS, TYPE, COUNT, VERSION, RESULT, CREATE_TIME, UPDATE_TIME)  VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
        int resRow = jdbcTemplate.update(sql, ps -> {
            ps.setString(1, transaction.getXid().getGlobalTransactionId());
            ps.setString(2, transaction.getXid().getBranchQualifier());
            ps.setString(3, domain);
            ps.setBytes(4, serializer.serialize(transaction.getParticipants()));
            String PARTNER_STR = "";
            try {
                PARTNER_STR = JSON.toJSONString(transaction.getParticipants());
            } catch (Exception e) {
                e.printStackTrace();
            }
            ps.setString(5, PARTNER_STR);
            ps.setInt(6, transaction.getStatus().value());
            ps.setInt(7, transaction.getRole().value());
            ps.setInt(8, transaction.getCount());
            ps.setLong(9, transaction.getVersion());
            ps.setInt(10, Result.INITIAL.value());
            ps.setTimestamp(11, new java.sql.Timestamp(transaction.getCreateTime().getTime()));
            ps.setTimestamp(12, new java.sql.Timestamp(transaction.getLastUpdateTime().getTime()));
        });
        return resRow;
    }

    @Override
    public int update(Transaction transaction) {
        String sql = "UPDATE " + getTableName() + " SET PARTNER = ?,PARTNER_STR=?, STATUS = ?, COUNT = ?,VERSION = VERSION + 1, UPDATE_TIME = ? WHERE GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ? AND VERSION = ? AND DOMAIN = ? ";
        int resRow = jdbcTemplate.update(sql, ps -> {
            ps.setBytes(1, serializer.serialize(transaction.getParticipants()));
            String PARTNER_STR = "";
            try {
                PARTNER_STR = JSON.toJSONString(transaction.getParticipants());
            } catch (Exception e) {
                e.printStackTrace();
            }
            ps.setString(2, PARTNER_STR);
            ps.setInt(3, transaction.getStatus().value());
            ps.setInt(4, transaction.getCount());
            ps.setTimestamp(5, new Timestamp(transaction.getLastUpdateTime().getTime()));
            ps.setString(6, transaction.getXid().getGlobalTransactionId());
            ps.setString(7, transaction.getXid().getBranchQualifier());
            ps.setLong(8, transaction.getVersion());
            ps.setString(9, domain);
        });
        transaction.setVersion(transaction.getVersion() + 1);
        return resRow;
    }

    @Override
    public int update(Transaction transaction, Result result) {
        int resRow = 0;
        try {
            transaction.setLastUpdateTime(new Date());
            String sql = "UPDATE " + getTableName() + " SET STATUS=?,RESULT = ?,VERSION = VERSION + 1,COUNT = ?,UPDATE_TIME=? WHERE GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ?  AND DOMAIN = ? AND VERSION=?";
            resRow = jdbcTemplate.update(sql, ps -> {
                ps.setInt(1, transaction.getStatus().value());
                ps.setInt(2, result.value());
                ps.setInt(3, transaction.getCount());
                ps.setTimestamp(4, new java.sql.Timestamp(transaction.getLastUpdateTime().getTime()));
                ps.setString(5, transaction.getXid().getGlobalTransactionId());
                ps.setString(6, transaction.getXid().getBranchQualifier());
                ps.setString(7, domain);
                ps.setLong(8, transaction.getVersion());
            });
            transaction.setVersion(transaction.getVersion() + 1);
        } catch (Exception e) {
            log.error("update log fail!", e);
        }
        return resRow;
    }

    @Override
    public Transaction findByXid(Xid xid) {
        String sql = "SELECT ID,GLOBAL_TX_ID, BRANCH_QUALIFIER, DOMAIN, PARTNER, STATUS, TYPE, COUNT, VERSION, RESULT, CREATE_TIME, UPDATE_TIME  FROM " + getTableName() + " WHERE  GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ?";
        List<Transaction> query = jdbcTemplate.query(sql, new String[]{xid.getGlobalTransactionId(), xid.getBranchQualifier()}, new TransactionMapper());
        if (query != null && query.size() > 0) return query.get(0);
        return null;
    }

    @Override
    public List<RecoveryInfo> findAllUnmodifiedSince() {
        String sql = "SELECT B.*,A.STATUS MSTATUS,A.RESULT MRESULT FROM ( SELECT GLOBAL_TX_ID,STATUS,RESULT  FROM " + getTableName() + " WHERE  TYPE=1 ORDER BY CREATE_TIME ) A LEFT JOIN " + getTableName() + " B ON A.GLOBAL_TX_ID=B.GLOBAL_TX_ID WHERE B.RESULT IN(-1,0) AND B.DOMAIN=?";
        return jdbcTemplate.query(sql, new String[]{domain}, new RecoveryInfoMapper());
    }

    @Override
    public int delete() {
        String sql = "DELETE FROM " + getTableName() + " WHERE GLOBAL_TX_ID IN( SELECT GLOBAL_TX_ID FROM ( SELECT GLOBAL_TX_ID, SUM(CASE WHEN RESULT=1 THEN 1 ELSE 0 END) OK, COUNT(GLOBAL_TX_ID) ZS FROM " + getTableName() + " GROUP BY GLOBAL_TX_ID ) T WHERE OK=ZS )";
        return jdbcTemplate.update(sql);
    }

    private String getTableName() {
        return StringUtils.isNotEmpty(tbSuffix) ? ("TCC_TRANSACTION" + tbSuffix) : "TCC_TRANSACTION";
    }

    class TransactionMapper implements RowMapper<Transaction> {
        @Override
        public Transaction mapRow(ResultSet r, int i) throws SQLException {
            Transaction transaction = new Transaction();
            Xid xid = new Xid(r.getString("GLOBAL_TX_ID"), r.getString("BRANCH_QUALIFIER"));
            List<Participant> participants = (List<Participant>) serializer.deserialize(r.getBytes("PARTNER"));
            transaction.setRole(Role.valueOf(r.getInt("TYPE")));
            transaction.setStatus(Status.valueOf(r.getInt("STATUS")));
            transaction.setCount(r.getInt("COUNT"));
            transaction.setCreateTime(r.getTimestamp("CREATE_TIME"));
            transaction.setLastUpdateTime(r.getTimestamp("UPDATE_TIME"));
            transaction.setVersion(r.getInt("VERSION"));
            transaction.setXid(xid);
            transaction.setParticipants(participants);
            return transaction;
        }

    }

    class RecoveryInfoMapper implements RowMapper<RecoveryInfo> {
        @Override
        public RecoveryInfo mapRow(ResultSet r, int i) throws SQLException {
            RecoveryInfo info = new RecoveryInfo();
            Xid xid = new Xid(r.getString("GLOBAL_TX_ID"), r.getString("BRANCH_QUALIFIER"));
            List<Participant> participants = (List<Participant>) serializer.deserialize(r.getBytes("PARTNER"));
            info.setRole(Role.valueOf(r.getInt("TYPE")));
            info.setStatus(Status.valueOf(r.getInt("STATUS")));
            info.setCount(r.getInt("COUNT"));
            info.setCreateTime(r.getTimestamp("CREATE_TIME"));
            info.setLastUpdateTime(r.getTimestamp("UPDATE_TIME"));
            info.setVersion(r.getInt("VERSION"));
            info.setXid(xid);
            info.setParticipants(participants);
            info.setMstatus(Status.valueOf(r.getInt("MSTATUS")));
            info.setResult(Result.valueOf(r.getInt("RESULT")));
            info.setMresult(Result.valueOf(r.getInt("MRESULT")));
            return info;
        }

    }

}